/* 
 * A UDP client that gets a line from the user, send the request to the
 * server, if no reply in timeo seconds, resends the packet again, and double 
 * t to 2t (exponential backoff)
 */ 

#include "networking.h"
#include <setjmp.h>

/*
 * convenient global variables
 */
int                       debug=0;
uint16_t                  serverport=7;
char                     *hostname=NULL;


int	                  sockfd;
struct sockaddr_in	  serveraddr;

int                       numrequest = 3;   // number of requests we want to sent 
int                       requestID = 100; // initial request ID

int	                  timeoinit = 1;  // initial timeout value
int                       timeomax  = 128;
int	                  timeo=1;        // timeout value

struct msghdr	          msgsend, msgrecv;	/* assumed init to 0 */
struct hdr {
  uint32_t reqID;	  /* request ID # */
} sendhdr, recvhdr;

void	                  sig_timeo_alarm(int signo);
sigjmp_buf	          jmpbuf;

ssize_t process_one_request(void *outbuf, size_t ocount,
			    void *inbuf,  size_t icount,
			    SA   *destaddr, socklen_t destlen);


void
sig_timeo_alarm(int signo)
{
  siglongjmp(jmpbuf, 1);
}


static void
parse_args(int argc, char** argv) 
{
  int n = 1;
  while (n < argc) {
    if (strcmp(argv[n], "-host") == 0) {
      hostname = argv[++n];
    } else if (strcmp(argv[n], "-debug") == 0) {
      debug = 1;
    } else if (strcmp(argv[n], "-port") == 0) {
      serverport = atoi(argv[++n]);
    } else if (strcmp(argv[n], "-n") == 0) {
      numrequest = atoi(argv[++n]);
    } else
      err_quit("usage: %s [-host <hostname>] [-port <port>] [-debug] [-n n]", argv[0]);
    n++;
  }
  if (hostname == NULL)
    err_quit("usage: %s [-host <hostname>] [-port <port>] [-debug] [-n n]", argv[0]);
}

int
main(int argc, char **argv)
{
  struct in_addr		**pptr;
  struct hostent		 *hp;

  parse_args(argc, argv);

  // get server address
  if ( (hp = gethostbyname(hostname)) == NULL) {
    err_quit("Cannot resovle hostname!");
  }

  if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
    err_quit("Cannot create UDP socket!");
  }
  
  // parse the addresses
  pptr = (struct in_addr **) hp->h_addr_list;
  // use the first address only
  bzero(&serveraddr, sizeof(serveraddr));
  serveraddr.sin_family = AF_INET;
  serveraddr.sin_port = htons(serverport);
  memcpy(&serveraddr.sin_addr, *pptr, sizeof(struct in_addr));

  // Simulate a request: read one line from the user, send the 
  // line to the server as a request

  char cmdline[MAXLINE], reply[MAXLINE];
  while (numrequest-- > 0) {
    if (fgets(cmdline, sizeof(cmdline), stdin) == NULL) {
      // no request, EOF
      err_quit("user done");
    }
    if (process_one_request(cmdline, 
			    strlen(cmdline),
			    reply,
			    sizeof(reply),
			    (SA *)&serveraddr,
			    sizeof(serveraddr)) < 0) {// no reply
      err_ret("No reply for request %s\n", requestID);
    } else {
      fputs(reply, stdout);
    }
    requestID++;
  }
}

ssize_t process_one_request(void *outbuf, size_t ocount,
			    void       *inbuf,  size_t icount,
			    SA   *destaddr, socklen_t destlen)
{
  struct iovec	iovsend[2], iovrecv[2];
  size_t       recvsize;

  sendhdr.reqID = requestID;

  msgsend.msg_name = (void *)destaddr;
  msgsend.msg_namelen = destlen;
  msgsend.msg_iov = iovsend;
  msgsend.msg_iovlen = 2;
  iovsend[0].iov_base = &sendhdr;
  iovsend[0].iov_len = sizeof(struct hdr);
  iovsend[1].iov_base = outbuf;
  iovsend[1].iov_len = ocount;
  
  msgrecv.msg_name = NULL;
  msgrecv.msg_namelen = 0;
  msgrecv.msg_iov = iovrecv;
  msgrecv.msg_iovlen = 2;
  iovrecv[0].iov_base = &recvhdr;
  iovrecv[0].iov_len = sizeof(struct hdr);
  iovrecv[1].iov_base = inbuf;
  iovrecv[1].iov_len = icount;

  timeo = timeoinit;

  signal(SIGALRM, sig_timeo_alarm);

  if (debug) fprintf(stderr, "send request %4d\n", sendhdr.reqID);

 sendagain:
  sendmsg(sockfd, &msgsend, 0);
  alarm(timeo);
  
  if (sigsetjmp(jmpbuf, 1) != 0) {   // no response
    timeo *= 2;
    if (debug) fprintf(stderr, "resend request %4d with timeo=%d\n", sendhdr.reqID, timeo);
    if (timeo > timeomax) {
      return(-1);
    }
    goto sendagain;
  }
  
  if (debug) fprintf(stderr, "receive reply for request %4d\n", sendhdr.reqID);
  do {
    recvsize = recvmsg(sockfd, &msgrecv, 0);
  } while (recvsize < sizeof(struct hdr) || recvhdr.reqID != sendhdr.reqID);
  
  alarm(0);			        /* stop SIGALRM timer */
  return(recvsize - sizeof(struct hdr));	/* return size of received datagram */
}
